﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Web;
using System.Globalization;
using System.Text.RegularExpressions;
using System.IO;

namespace GBPVRLibrary.MetadataLibrary.TheTVDBInterface
{
    public class TheTVDB
    {
        public const string APIKey = "B32D5E15749F2D9E";
        private static string _Mirror;

        public static Regex RegexSeason { get { return new Regex(@"(?<=(([\s]+|[\(\{\[])(s)?))[\d]+(?=([x\-e\.]))", RegexOptions.IgnoreCase); } }
        public static Regex RegexEpisode { get { return new Regex(@"(?<=([\d]+[x\-e\.]))[\d]+(?=((([\s]+|[\)\]\}]))|(\.[\w]+$)|$))", RegexOptions.IgnoreCase); } }
        public static Regex RegexTitle { get { return new Regex(@"^(.*?)(?=([\s]*\-[\s]+(s)?[\d]+))", RegexOptions.IgnoreCase); } }
        public static Regex RegexSubtitle { get { return new Regex(@"(?<=([\d]+[\s]*(\-)[\s]+))(.*?)$", RegexOptions.IgnoreCase); } }

        private static string Mirror
        {
            get
            {
                if (String.IsNullOrEmpty(_Mirror))
                {
                    string Url = String.Format("http://www.thetvdb.com/api/{0}/mirrors.xml", APIKey);
                    XDocument doc = LoadDocument(Url);
                    string[] mirrors = (from m in doc.Element("Mirrors").Elements("Mirror") select m.Element("mirrorpath").Value).ToArray();
                    if (mirrors.Length == 0)
                    {
                        WebSiteHelper.DeleteCacheFile(Url);
                        return "http://www.thetvdb.com/";
                    }
                    Random random = new Random(DateTime.Now.Millisecond);
                    _Mirror = mirrors[random.Next(0, mirrors.Length-1)];
                }
                return _Mirror;
            }
        }

        /// <summary>
        /// This interface allows you to find the id of a series based on its name. 
        /// </summary>
        /// <param name="Title">This is the string you want to search for. If you search for "Lost", the first 10 series that include the word "Lost" in their name will be returned. If there is an exact match for the parameter, it will be the first result returned. </param>
        /// <param name="Language">This is the abbreviation for the language you want to search in. This may also be set to all, which will search ALL translations for &lt;seriesname&gt;. If omitted, it will default to English.</param>
        /// <returns>a list of Series matching the search pattern</returns>
        public List<TheTVDBSeries> GetSeries(string Title, string Language)
        {
            string Url = String.Format("{0}/api/GetSeries.php?seriesname={1}", Mirror, HttpUtility.UrlEncode(Title), String.IsNullOrEmpty(Language) ? "" : String.Format("&language={0}", Language));
            XDocument doc = LoadDocument(Url);
            if (doc == null)
            {
                WebSiteHelper.DeleteCacheFile(Url);
                return new List<TheTVDBSeries>();
            }
            List<TheTVDBSeries> results = (from s in doc.Element("Data").Elements("Series")
                                           select TheTVDBSeries.LoadSeries(s, Mirror)
                                          ).ToList();
            if(results.Count == 0)
                WebSiteHelper.DeleteCacheFile(Url);
            return results;
        }

        public TheTVDBEpisode GetSeriesEpisode(int EpisodeId, string Language)
        {
            Language = Language ?? "en";
            string Url = String.Format("{0}/api/{1}/episodes/{2}/{3}.xml", Mirror, APIKey, EpisodeId, Language);
            TheTVDBEpisode episode = null;
            try
            {
                XDocument doc = LoadDocument(Url);
                if (doc == null)
                    return null;
                episode = new TheTVDBEpisode(doc.Element("Data").Element("Episode"), Mirror, true, Language);
                return episode;
            }
            finally
            {
                if (episode == null)
                    WebSiteHelper.DeleteCacheFile(Url);

            }
        }

        /// <summary>
        /// Loads a list of episodes for a particular series
        /// </summary>
        /// <param name="SeriesId">the id of the series</param>
        /// <param name="Language">the language of the series</param>
        /// <returns>a list of all the episodes for a series</returns>
        public List<TheTVDBEpisode> GetSeriesEpisodes(int SeriesId, string Language)
        {
            string Url = String.Format("{0}/api/{1}/series/{2}/all/{3}.xml", Mirror, APIKey, SeriesId, String.IsNullOrEmpty(Language) ? "en" : Language);
            XDocument doc = LoadDocument(Url);
            if (doc == null)
            {
                WebSiteHelper.DeleteCacheFile(Url);
                return new List<TheTVDBEpisode>();
            }
            List<TheTVDBEpisode> episodes = (from ep in doc.Element("Data").Elements("Episode")
                                             select new TheTVDBEpisode(ep, Mirror, false, Language)
                                             {
                                                 Duration = new TimeSpan(0, int.Parse(doc.Element("Data").Element("Series").Element("Runtime").Value), 0),
                                                 SeriesName = doc.Element("Data").Element("Series").Element("SeriesName").Value,
                                                 Genre = doc.Element("Data").Element("Series").Element("Genre").Value.Split(new char[]{'|'}, StringSplitOptions.RemoveEmptyEntries).ToList()
                                             }
                                            ).ToList();
            if (episodes.Count == 0)
                WebSiteHelper.DeleteCacheFile(Url);
            return episodes;
        }

        public List<string> GetSeriesBanners(int SeriesId)
        {
            string Url = String.Format("{0}/api/{1}/series/{2}/banners.xml", Mirror, APIKey, SeriesId);
            XDocument doc = LoadDocument(Url);
            if (doc == null)
            {
                WebSiteHelper.DeleteCacheFile(Url);
                return new List<string>();
            }
            List<string> banners = (from b in doc.Element("Banners").Elements("Banner")
                                    select String.Format("{0}/{1}", Mirror, b.Element("BannerPath").Value)
                                    ).ToList();
            if (banners.Count == 0)
                WebSiteHelper.DeleteCacheFile(Url);
            return banners;
        }

        public List<TheTVDBBanner> GetSeriesBanners(int SeriesId, GBPVRLibrary.MetadataLibrary.TheTVDBInterface.TheTVDBBanner.BannerType BannerType)
        {
            return GetSeriesBanners(SeriesId, BannerType, 1000);
        }

        public List<TheTVDBBanner> GetSeriesBanners(int SeriesId, GBPVRLibrary.MetadataLibrary.TheTVDBInterface.TheTVDBBanner.BannerType BannerType, int NumberToDownload)
        {
            string Url = String.Format("{0}/api/{1}/series/{2}/banners.xml", Mirror, APIKey, SeriesId);
            XDocument doc = LoadDocument(Url);
            if (doc == null)
            {
                WebSiteHelper.DeleteCacheFile(Url);
                return new List<TheTVDBBanner>();
            }
            int count = 0;
            List<TheTVDBBanner> banners = (from b in doc.Element("Banners").Elements("Banner")
                                           where ((b.Element("BannerType").Value == "fanart" && (BannerType & TheTVDBBanner.BannerType.FanArt) == TheTVDBBanner.BannerType.FanArt) ||
                                                 (b.Element("BannerType").Value == "season" && (BannerType & TheTVDBBanner.BannerType.Season) == TheTVDBBanner.BannerType.Season) ||
                                                 (b.Element("BannerType").Value == "series" && (BannerType & TheTVDBBanner.BannerType.Series) == TheTVDBBanner.BannerType.Series) ||
                                                 (b.Element("BannerType").Value == "poster" && (BannerType & TheTVDBBanner.BannerType.Poster) == TheTVDBBanner.BannerType.Poster)
                                                 ) && count++ < NumberToDownload
                                           select new TheTVDBBanner(String.Format("{0}/banners/{1}", Mirror, b.Element("ThumbnailPath") == null ? b.Element("BannerPath").Value : b.Element("ThumbnailPath").Value))
                                           {
                                               Name = b.Element("BannerPath").Value.Substring(b.Element("BannerPath").Value.LastIndexOf("/")+1),
                                               Unit = b.Element("BannerType2") != null ? b.Element("BannerType2").Value : "",
                                               BannerUrl = String.Format("{0}/banners/{1}", Mirror, b.Element("BannerPath").Value),
                                               Type = b.Element("BannerType").Value == "fanart" ? TheTVDBBanner.BannerType.FanArt : 
                                                     ( b.Element("BannerType").Value == "season" ? TheTVDBBanner.BannerType.Season : TheTVDBBanner.BannerType.Series)
                                           }
                                           ).ToList();
            if (banners.Count == 0)
                WebSiteHelper.DeleteCacheFile(Url);
            return banners;
        }

        public List<TheTVDBBanner> GetSeasonPosters(int SeriesId, int SeasonNumber)
        {
            string Url = String.Format("{0}/api/{1}/series/{2}/banners.xml", Mirror, APIKey, SeriesId);
            XDocument doc = LoadDocument(Url);
            if (doc == null)
            {
                WebSiteHelper.DeleteCacheFile(Url);
                return new List<TheTVDBBanner>();
            }
            List<TheTVDBBanner> banners = (from b in doc.Element("Banners").Elements("Banner")
                                           where b.Element("BannerType").Value == "season" && b.Element("Season") != null && b.Element("Season").Value == SeasonNumber.ToString()
                                           select new TheTVDBBanner(String.Format("{0}/banners/{1}", Mirror, b.Element("ThumbnailPath") == null ? b.Element("BannerPath").Value : b.Element("ThumbnailPath").Value))
                                           {
                                               Name = b.Element("BannerPath").Value.Substring(b.Element("BannerPath").Value.LastIndexOf("/") + 1),
                                               Unit = b.Element("BannerType2") != null ? b.Element("BannerType2").Value : "",
                                               BannerUrl = String.Format("{0}/banners/{1}", Mirror, b.Element("BannerPath").Value),
                                               Type = b.Element("BannerType").Value == "fanart" ? TheTVDBBanner.BannerType.FanArt :
                                                     (b.Element("BannerType").Value == "season" ? TheTVDBBanner.BannerType.Season : TheTVDBBanner.BannerType.Series)
                                           }
                                           ).ToList();
            if (banners.Count == 0)
                WebSiteHelper.DeleteCacheFile(Url);
            return banners;
        }

        internal static XDocument LoadDocument(string Url)
        {
            try
            {
                GBPVRLibrary.UtilityLibrary.LogWriter.Log(String.Format(@"TheTVDB - Loading Url: {0}", Url));
                string Filename = WebSiteHelper.CacheFilename(Url);
                FileInfo fi = new FileInfo(Filename);
                if (fi.Exists && fi.LastWriteTime >= DateTime.Now.AddDays(-5))
                {
                    GBPVRLibrary.UtilityLibrary.LogWriter.Log(String.Format(@"TheTVDB - Using Cache file: {0}", Filename));
                    return XDocument.Load(Filename);
                }
                XDocument doc = XDocument.Load(Url);
                try
                {
                    doc.Save(Filename); // not a showstopper if we cant save a local copy of the file
                }
                catch (Exception) { }
                return doc;
            }
            catch (Exception) { return null; }
        }

        public Metadata FindEpisodeMetadata(string SearchString, ref TheTVDBSeries Series)
        {
            TheTVDBEpisode episode = FindEpisode(SearchString, ref Series);
            return episode == null ? null : episode.ConvertToVideoMetadata(Series.EpisodeOrder == TheTVDBSeries.EpisodeOrderType.Aired);
        }


        /// <summary>
        /// Tries to find the episode using a search string
        /// </summary>
        /// <param name="SearchString">the search pattern, note: the best pattern is "SeriesName - SeasonxEpisodes - EpisodeName"</param>
        /// <returns>an episode if found</returns>
        public TheTVDBEpisode FindEpisode(string SearchString, ref TheTVDBSeries Series)
        {
            string title = RegexTitle.IsMatch(SearchString) ? RegexTitle.Match(SearchString).Value : null;
            string subtitle = RegexSubtitle.IsMatch(SearchString) ? RegexSubtitle.Match(SearchString).Value : null;
            int? epnumber = RegexEpisode.IsMatch(SearchString) ? int.Parse(RegexEpisode.Match(SearchString).Value) as int? : null;
            int? season = RegexSeason.IsMatch(SearchString) ? int.Parse(RegexSeason.Match(SearchString).Value) as int? : null;

            if (String.IsNullOrEmpty(title) && Series == null)
                return null;
            else if(String.IsNullOrEmpty(subtitle) && season == null && epnumber == null)
                return null;
            Series = Series ?? FindSeries(title);
            if (Series == null)
                return null;
            List<TheTVDBEpisode> episodes = GetSeriesEpisodes(Series.Id, null);
            //if (season != null && RegexEpisode.IsMatch(SearchString) && RegexEpisode.Matches(SearchString).Count > 1)
            //{
                List<TheTVDBEpisode> results = new List<TheTVDBEpisode>();
                List<int> epnumbers = new List<int>();
                foreach (Match r in RegexEpisode.Matches(SearchString))
                    epnumbers.Add(int.Parse(r.Value));
                // check the episodes name first
                if (!String.IsNullOrEmpty(subtitle))
                {
                    foreach (TheTVDBEpisode episode in episodes)
                    {
                        if (MetadataFinder.CompareStrings(episode.Name, subtitle) < 3)
                        {
                            results.Add(GetSeriesEpisode(episode.Id, null));
                        }
                    }
                }
                if (results.Count == 0)
                {
                    foreach (TheTVDBEpisode episode in episodes)
                    {
                        int epNumber = (Series.EpisodeOrder == TheTVDBSeries.EpisodeOrderType.DVD && episode.DVDEpisodeNumber != null) ? (int)episode.DVDEpisodeNumber.Value : episode.EpisodeNumber;
                        int seasonNumber = (Series.EpisodeOrder == TheTVDBSeries.EpisodeOrderType.DVD && episode.DVDSeason != null) ? (int)episode.DVDSeason.Value : episode.SeasonNumber;
                        if (seasonNumber != season)
                            continue;
                        foreach (int epnum in epnumbers)
                        {
                            if (epnum == epNumber)
                            {
                                results.Add(GetSeriesEpisode(episode.Id, null));
                                break;
                            }
                        }

                    }
                }
                if (results.Count == 0)
                    return null;
                else if (results.Count == 1)
                    return results[0];
                TheTVDBEpisode result = results[0];
                result.Overview = String.Format("{0}{1}{2}", result.Name, Environment.NewLine, result.Overview);
                for (int i = 1; i < results.Count; i++)
                {
                    if (result.Duration != null && results[i].Duration != null)
                        result.Duration = result.Duration.Value.Add(results[i].Duration.Value);
                    result.Overview += Environment.NewLine + Environment.NewLine + results[i].Name + Environment.NewLine + results[i].Overview;
                }
                if (Regex.IsMatch(result.Name, @"\([\d]*\)$"))
                    result.Name = Regex.Replace(result.Name, @"[\s]*\([\d]*\)$", "");
                return result;

            //}
            //else
            //{
            //    foreach (TheTVDBEpisode episode in episodes)
            //    {
            //        if (epnumber != null && season != null)
            //        {
            //            if (episode.SeasonNumber == season && episode.EpisodeNumber == epnumber)
            //            {
            //                return GetSeriesEpisode(episode.Id, null);
            //            }
            //        }
            //        if (!String.IsNullOrEmpty(subtitle) && StringsMatch(episode.Name, subtitle))
            //            return GetSeriesEpisode(episode.Id, null);
            //    }
            //}
            return null;
        }

        public TheTVDBSeries FindSeries(string SeriesName)
        {
            if (String.IsNullOrEmpty(SeriesName))
                return null;
            List<TheTVDBSeries> series = GetSeries(SeriesName, null);
            if (series == null || series.Count == 0)
                return null;
            foreach (TheTVDBSeries s in series)
                if (StringsMatch(Regex.Replace(s.Name, @"[\-\.\\/\|', ]", ""), Regex.Replace(SeriesName, @"[\-\.\\/\|', ]", "")))
                    return s;
            return series[0];
        }

        private bool StringsMatch(string s1, string s2)
        {
            return s1.ToLower().Replace("'", "").Replace(" ", "") == s2.ToLower().Replace("'", "").Replace(" ", "");
        }
    }
}
